package com.young.common.client.log.core;

import com.young.common.client.log.model.CommonContextDTO;
import com.young.common.util.CollectionUtil;
import com.young.common.util.StringUtils;
import com.young.interfaces.log.model.LogDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedList;

/**
 * Log上下文持有者
 *
 * @author wangjiyu@imdada.cn
 * @create 2019/3/6
 */
public class LogContextHolder {
    private static final Logger logger = LoggerFactory.getLogger(LogContextHolder.class);
    /**
     * 一个调用链会执行N个方法,对应着N个LogDTO
     * 相当于调用链的栈, 先进后出, 最后一个log就是当前执行方法的log, 倒数第2个就是当前方法的爸爸log, 以此类推
     */
    private static final ThreadLocal<LinkedList<LogDTO>> logDTOContext = new ThreadLocal<LinkedList<LogDTO>>();
    /**
     * 链路共有属性上下文, 例如用户,业务id等链路的公共属性放到这里
     */
    private static final ThreadLocal<CommonContextDTO> TRACE_COMMON_CONTEXT = new ThreadLocal<CommonContextDTO>();

    /**
     * 设置当前方法上下文的logDTO
     *
     * @param logDTO
     */
    static void setLogDTO(LogDTO logDTO) {
        if (logDTO == null) {
            return;
        }
        LinkedList<LogDTO> linkedList = logDTOContext.get();
        if (linkedList == null) {
            linkedList = new LinkedList<LogDTO>();
            // 初始化链路
            logDTOContext.set(linkedList);
            // 标记第一个节点
            logDTO.setSpanFirstFlag(true);
            // 初始化链路公共参数
            CommonContextDTO commonContextDTO = CommonContextDTO.build(logDTO);
            TRACE_COMMON_CONTEXT.set(commonContextDTO);
        } else {
            // 尝试设置公共参数
            CommonContextDTO commonContextDTO = TRACE_COMMON_CONTEXT.get();
            if (commonContextDTO != null) {
                commonContextDTO.fillTo(logDTO);
            }
        }
        linkedList.add(logDTO);
    }

    /**
     * 获取当前方法上下文的logDTO
     *
     * @return
     */
    public static LogDTO getLogDTO() {
        LinkedList<LogDTO> linkedList = logDTOContext.get();
        if (linkedList == null) {
            return null;
        }
        return linkedList.getLast();
    }

    /**
     * 设置链路(除去已经结束的节点)的业务id
     *
     * @param businessId
     */
    public static void setTraceBusinessId(String businessId) {
        if (StringUtils.isBlank(businessId)) {
            return;
        }
        LinkedList<LogDTO> linkedList = logDTOContext.get();
        if (CollectionUtil.isNotEmpty(linkedList)) {
            linkedList.forEach(item -> item.setBusinessId(businessId));
        }
        // 放到链路公参中
        CommonContextDTO commonContextDTO = TRACE_COMMON_CONTEXT.get();
        if (commonContextDTO != null) {
            commonContextDTO.setBusinessId(businessId);
        }
    }

    /**
     * 移除当前上下文的logDTO
     *
     * @param logDTO 有值时用来验证删除的和预想的是否一致,根据uuid
     */
    static void removeLogDTO(LogDTO logDTO) {
        if (logDTO == null) {
            return;
        }
        LinkedList<LogDTO> linkedList = logDTOContext.get();
        if (CollectionUtil.isEmpty(linkedList)) {
            logger.error("[接口监控日志上下文-LogContextHolder.removeLogDTO] 严重!!!链路log上下文链表为空!节点UUID={}", logDTO.getUuid());
            return;
        }

        //删除最后一个
        LogDTO removeLog = linkedList.removeLast();

        // 检查移除的是不是目标log, 正常情况应该是
        if (!StringUtils.equals(removeLog.getUuid(), logDTO.getUuid())) {
            logger.error("[接口监控日志上下文-LogContextHolder.removeLogDTO] 严重!!!接口监控日志上下文移除异常:移除项与验证项不一致,移除项UUID={},预期UUID={}", removeLog.getUuid(), logDTO.getUuid());
        }
        // 检查链路根节点移除后list是不是空的, 正常情况应该是空的才对
        if (logDTO.isSpanFirstFlag() && CollectionUtil.isNotEmpty(linkedList)) {
            logger.error("[接口监控日志上下文-LogContextHolder.removeLogDTO] 严重!!!移除链路根节点后上下文链表仍然有数据!根节点UUID={},剩余节点信息={}", logDTO.getUuid(), linkedList);
        }

        // 如果是根节点, 则清空上下文
        if (logDTO.isSpanFirstFlag()) {
            logDTOContext.remove();
            TRACE_COMMON_CONTEXT.remove();
        }
    }
}
